package d20chat;

import java.io.IOException;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Scanner;



/**
 * Handles the flow of the entire server.
 * Deals with storing all the users connected to the server, passing messages
 * between users, server commands, gm commands.
 */
public class Server {
    
    private String serverIP; //stores the server's IP address
    private String password; //the password for joining the server
    private boolean lock; //is the room locked (true) or open to new players (false)
    private String gmID; //name of the gm
    private boolean visibleSheets = true; //can players see other players character_sheets(true) or not (false)
    private User[] users;
    private ServerListener serverthread;
    private D20Chatui tempui;
    private LinkedList<Object> joiningList;
    private D20Chat tempmain;
    private String currentmap;
    
    
    /**
     *Used when a request to join room message is recieved
     */
/**
     * Adds a user to the server.
     * @param connection socket that the new user is connected to.
     */
    public void joinRoom(Socket connection)
    {
        String names = "";
        User tempUser;       

        // add them to joiningList
        User tempuser = new User();

        // passing connection to store it in User part
        tempuser.setSocket(connection);

        if( joiningList.isEmpty() )
        {
            tempuser.setCharacterName( "GM" );
        }

        joiningList.add(tempuser);

        if( !password.isEmpty() )
        {
            //there is a password for the room, so request password from user
            serverthread.SendMessage( "server request password ", connection);
        }
        else
        {
            //no password so move on
            //request user info
            serverthread.SendMessage( "server request requestUserInfo ", connection);

                    //send the user a list of the people currently in the room
                    names = "\u8594";
                    for (int j = 0; j < joiningList.size()-1; j++ )
                    {

                        tempUser = (User) joiningList.get(j);
                        names = names + tempUser.getUsername() + " ( " + tempUser.getCharacterName() + " )\u8594" ;

                    }
                    names = "server request playersList null " + names;
                    serverthread.SendMessage( names, connection);
         }
    }

    
    /**
     *Serverthreads will pass message to the server here 
     */
/**
     * Used to give a message to the server.
     * Server parses the message, checks who sent it, and evaluates what should be
     * done with it. Usually called by the thread that is handling the connection
     * associated with the user sending the message.
     * @param message Message recieved by the server.
     * @param connection Socket that the message is coming from.
     */
    public void GiveMessage(String message, Socket connection)
    {  // ** My recieved message
        User temp_list, templist2;
        Socket temp_socket;
        int pos;
        
       //check connection to make sure they have permissions with command verifyGM()
       //parse message to see who to send it to
       //if response from requestinfo, change info in userlist
        String temp_message = null;
        String first=null,second=null,third=null,fourth=null,fifth=null;
        
        //find the first five words of the message if they exist
       Scanner wordScanner = new Scanner( message );
       if( wordScanner.hasNext() )
       {
            first = wordScanner.next();
       }

       if( wordScanner.hasNext() )
       {
           second = wordScanner.next();
       }

       if( wordScanner.hasNext() )
       {
           third = wordScanner.next();
       }

       if( wordScanner.hasNext() )
       {
          fourth = wordScanner.next();
       }

       if( wordScanner.hasNext () )
       {
           fifth = wordScanner.next();
       }
       

        if( second.equals( "request" ) )
        {
            if( third.equals("userInfo") )
            {
                userInfo( message, connection );

            }
            else if( third.equals( "password" ) )
            {
                checkPassword( fifth, connection );
            }
            else if( third.equals( "changeName" ) )
            {
                changeName( fourth, connection );
            }
            else if( third.equals( "hideProfile") )
            {
                hideProfile( connection );
            }
            else if( verifyUser( first, connection ) )
            {
                if( third.equals( "profile" ) )
                {
                    profile( fourth, connection );
                }
                else if( third.equals( "charsheet" ))
                {
                    charsheet(fourth, connection, message);
                }
                else if( third.equals( "sendcharsheet" ))
                {
                    sendcharsheet(fourth, connection, message);
                }
                else if( third.equals( "charNameChange" ) )
                {
                    charChangeName( message, connection);
                }
            }
                
        }
        else if ( second.equals("chat") )
        {
            if (third.equals("systemMessage") || third.equals("hide"))
            {
                serverthread.SendMessage(message, connection);
            }
            else if( verifyUser( first, connection ) )
            {
                if ( third.equals("say") || third.equals( "emote" ) )
                {
                    //as far as server is conserned emote
                    //is the same as say, so handle them the same
                    say( message, connection);
                }
                else if (third.equals("tell"))
                {
                    tell( message, fourth, connection);
                }
             }

        }
        else if (second.equals("command"))
        {
            if ( verifyGM(connection) )
            {
                if ( third.equals("kick") )
                {
                    kick( message, fourth, connection);
                }
                else if( third.equals("silence") )
                {
                    silence( message, fourth, fifth, connection);
                }
                else if ( third.equals("charactersheet") )
                {
                    charactersheetViewing( message, fifth, connection);
                }
                else if( third.equals("makeGM") )
                {
                    makeGM( message, fourth, connection);
                }
                else if( third.equals( "setPassword" ) )
                {
                    setPassword( fifth, connection );
                }
                else if(third.equals("map"))
                {
                	if (fourth.equals("update"))
                	{
                		this.currentmap = message.substring(message.indexOf("DDMap"));
                		sendGroupMessage(message);
                		
                	}
                }
            }
            else
            {
               serverthread.SendMessage("server chat systemMessage server Silly player you are not the GM.", connection);
            }
        }
        else
        {
            serverthread.SendMessage("server chat systemMessage server Not a correct / command.", connection);
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void userInfo( String infoMessage, Socket connection )
    {
        String testName = "";
        int pos, namePos;
        User aUser;
        
            Scanner pieceScanner = new Scanner( infoMessage );
            pieceScanner.useDelimiter( "\u8594" );

            if( pieceScanner.hasNext() )
            {
                //junk piece
              pieceScanner.next();

            }
            if( pieceScanner.hasNext() )
            {
              testName = pieceScanner.next();
            }
            
            namePos = findByName(testName);
            pos = findByConnection(connection);
            
            if( namePos == pos )
            {
                //the name is yours, cool go ahead and update, but no one needs to know about it
                aUser = (User)joiningList.get(pos);
                String tempname = (String)aUser.setUserInfo(infoMessage);
            }
            else if( namePos == -1 )
            {
                //the name wasn't in use, cool go ahead and let everyone know
                if( !testName.isEmpty() )
                {
                    aUser = (User)joiningList.get(pos);

                    String previousname = aUser.getUsername();
                    String tempname = (String)aUser.setUserInfo(infoMessage);
                    //begin server announcement to all saying userinfo change
                    String tempmessage;

                    if(previousname.isEmpty())
                    {
                        tempmessage = tempname + " command joinRoom blah " + aUser.getCharacterName();
                        sendGroupMessage(tempmessage);

                    }
                    else if(!previousname.equals(tempname))
                    {
                        tempmessage = previousname + " request changeName " + tempname + " " + aUser.getCharacterName();
                        sendGroupMessage(tempmessage);
                    }
                }
            }
            else
            {
                //that's someone else's name
                serverthread.SendMessage("server chat systemMessage server The name " + testName + " is already in use. Please go to Options->Edit Profile and choose a new name.", connection);
            }
    }
    
    
    private void hideProfile(Socket connection) 
    {
        int pos;
        User aUser;
        pos = findByConnection( connection );
        
        aUser = (User)joiningList.get(pos);
        aUser.userProfile.hide();
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void setPassword( String newPassword, Socket connection )
    {
        
        if( newPassword.equals("none") )
        {
            password = "";
            String successMessage = "server chat systemMessage server The room is no longer passworded.";
            serverthread.SendMessage( successMessage, connection );
        }
        else
        {
            password = newPassword;
            String successMessage = "server chat systemMessage server You have successfully changed the password to : " + password + ".";
            serverthread.SendMessage( successMessage, connection );
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void changeName( String newName, Socket connection )
    {
        int pos, namePos;
        User aUser;
        
            
            namePos = findByName(newName);
            pos = findByConnection(connection);
            
            if( namePos == pos )
            {
                //the name is yours, cool go ahead and update incase it's a change in capatilization,
                //but no one needs to know about it
                aUser = (User)joiningList.get(pos);
                aUser.setUsername(newName);
            }
            else if( namePos == -1 )
            {
                //the name wasn't in use, cool go ahead and let everyone know
                if( !newName.isEmpty() )
                {
                    aUser = (User)joiningList.get(pos);

                    String previousname = aUser.getUsername();
                    aUser.setUsername(newName);
                    //begin server announcement to all saying userinfo change
                    String tempmessage;

                    if(previousname.isEmpty())
                    {
                        tempmessage = newName + " command joinRoom blah " + aUser.getCharacterName();
                        sendGroupMessage(tempmessage);

                    }
                    else if(!previousname.equals(newName))
                    {
                        tempmessage = previousname + " request changeName " + newName + " " + aUser.getCharacterName();
                        sendGroupMessage(tempmessage);
                    }
                }
            }
            else
            {
                //that's someone else's name
                serverthread.SendMessage("server chat systemMessage server The name " + newName + " is already in use. Please go to Options->Edit Profile and choose a new name.", connection);
            }
    }
        
        
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void charChangeName(String message, Socket connection)
    {
        int pos;
        User aUser;
        String newName = "player";
        
        Scanner pieceScanner = new Scanner( message );
        pieceScanner.useDelimiter( "\u8594" );

        if( pieceScanner.hasNext() )
        {
            //junk piece
          pieceScanner.next();

        }
        if( pieceScanner.hasNext() )
        {
          newName = pieceScanner.next();
        }
            
        pos = findByConnection( connection );
        if( pos != -1 )
        {
            aUser = (User)joiningList.get(pos);
            aUser.setCharacterName(newName);
            sendGroupMessage(message);
            
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void profile( String targetName, Socket connection )
    {
        int pos;
        User aUser;
        String info;
        
        pos = findByName(targetName);
        if( pos == -1)
        {
               serverthread.SendMessage("server chat systemMessage server " + targetName + " is not a player name.", connection);
        }
        else
        {
            aUser = (User)joiningList.get(pos);
            if( aUser.userProfile.getVisible() )
            {
                info = "you request profile back " + aUser.toString();  
                serverthread.SendMessage( info, connection);
            }
            else
            {
                serverthread.SendMessage("server chat systemMessage server " + targetName + " has chosen not to share their profile.", connection);
            }
        }

        
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void checkPassword( String enteredPassword, Socket connection )
    {
        String names = "";
        User tempUser;
        if( enteredPassword.equals(password) )
        {
                //request user info
                serverthread.SendMessage( "server request requestUserInfo ", connection);
                names = "\u8594";
                for (int j = 0; j<joiningList.size()-1; j++ )
                {

                    tempUser = (User) joiningList.get(j);
                    //names = names + tempUser.getUsername() + " " ;
                    names = names + tempUser.getUsername() + " ( " + tempUser.getCharacterName() + " )\u8594" ;

                }
                names = "server request playersList null " + names;
                serverthread.SendMessage( names, connection);
        }
        else
        {
            serverthread.SendMessage("server chat systemMessage server That is not the password for the room you were trying to join.", connection);

            try 
            {
                //remove them from the list
                connection.close();

            } 
            catch (IOException ex) 
            {
                ex.printStackTrace();
            }

        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void say( String sayMessage, Socket connection)
    {
        int pos;
        User aUser;
        String silenceReminder;
        
        pos = findByConnection(connection);
        aUser = (User)joiningList.get(pos);
        
        // check if silenced
        if ( !aUser.getSilenced() )
        {
              sendGroupMessage(sayMessage);
        }
        else
        {
            // send back to sender saying they are silenced
            silenceReminder = "server chat systemMessage server You are silenced, so cannot talk.";
            serverthread.SendMessage( silenceReminder, connection );
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void tell( String tellMessage, String targetName, Socket connection)
    {
        int pos;
        User aUser;
        Socket theirSocket;
        
        pos = findByName(targetName);
        if( pos == -1 )
        {
            serverthread.SendMessage("server chat systemMessage server " + targetName + " is not a player name.", connection);
        }
        else
        {
            aUser = (User)joiningList.get(pos);
            theirSocket = aUser.getSock();

            serverthread.SendMessage(tellMessage, theirSocket);
            serverthread.SendMessage(tellMessage, connection);
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void kick( String kickMessage, String targetName, Socket connection)
    {
        int pos;
        User aUser;
        Socket theirSocket;
        
        pos = findByName(targetName);
        
        if( pos == -1 )
        {
            serverthread.SendMessage("server chat systemMessage server " + targetName + " is not a player name.", connection);
        }
        if( pos == 0 )
        {
            serverthread.SendMessage("server chat systemMessage server You cannot kick yourself.", connection);
        }
        else
        {
            aUser = (User)joiningList.get(pos);
            theirSocket = aUser.getSock();
            try 
            {
                sendGroupMessage(kickMessage);
                //remove them from the list
                theirSocket.close();
            } 
            catch (IOException ex) 
            {
                ex.printStackTrace();
            }
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void silence( String silenceMessage, String targetName, String state, Socket connection)
    {
        int pos;
        User aUser;
        
        if ( targetName.equals("all") )
        {
            boolean temp_v = false;

            if ( state.equals("on") )
            {
                temp_v = true;
            }
            else if( state.equals( "off" ) )
            {
                temp_v = false;
            }
            
            for ( int i = 1; i < (joiningList.size()); i++ )
            {
                    aUser = (User)joiningList.get(i);
                    aUser.setSilenced(temp_v);
            }

            sendGroupMessage(silenceMessage);
        }
        else
        {
            pos = findByName(targetName);
            if( pos == -1 )
            {
                serverthread.SendMessage("server chat systemMessage server " + targetName + " is not a player name.", connection);
            }
            else
            {
                aUser = (User)joiningList.get(pos);

                if ( state.equals("on") )
                {
                    aUser.setSilenced(true);
                }
                else if ( state.equals("off"))
                {
                    aUser.setSilenced(false);
                }
                sendGroupMessage(silenceMessage);
            }

        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void charactersheetViewing( String charactersheetMessage, String state, Socket connection)
    {
        if ( state.equals("off") )
        {
            visibleSheets = false;
            sendGroupMessage(charactersheetMessage);
        }
        else if (state.equals("on"))
        {
            visibleSheets = true;
            sendGroupMessage(charactersheetMessage);
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void charsheet(String targetName, Socket connection, String message) 
    {

        int pos;
        User aUser;
        String info;
        if( visibleSheets == true || verifyGM(connection) )
        {
            pos = findByName(targetName);
            if( pos == -1)
            {
                serverthread.SendMessage("server chat systemMessage server " + targetName + " is not a player name.", connection);
            }
            else
            {
                aUser = (User)joiningList.get(pos);
                serverthread.SendMessage( message, aUser.getSock());
            }
        }
        else
        {
            serverthread.SendMessage("server chat systemMessage server The GM has not made charactersheets visible.", connection);
        }
    }

    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void sendcharsheet(String targetName, Socket connection, String message) 
    {

        int pos;
        User aUser;
        String info;
        
        if( visibleSheets == true || findByName(targetName) == 0 )
        {
            pos = findByName(targetName);
            if( pos == -1)
            {
                   serverthread.SendMessage("server chat systemMessage server " + targetName + " is not a player name.", connection);
            }
            else
            {
                aUser = (User)joiningList.get(pos);
                serverthread.SendMessage( message, aUser.getSock());
            }
        }
        else
        {
            serverthread.SendMessage("server chat systemMessage server The GM has not made charactersheets visible.", connection);
        }

    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void makeGM( String GMMessage, String targetName, Socket connection)
    {
        User tempUser;
        int pos;
        pos = findByName(targetName); 
        
        if( pos == -1)
        {
               serverthread.SendMessage("server chat systemMessage server " + targetName + " is not a player name.", connection);
        }
        else
        {
            tempUser = (User)joiningList.get(0);
            tempUser.setCharacterName( "player" );
            String playerChange = tempUser.getUsername() + " request charNameChange blah \u8594player\u8594";


            tempUser = (User)joiningList.get(pos);
            tempUser.setCharacterName( "GM" );
            String gmChange = tempUser.getUsername() + " request charNameChange blah \u8594GM\u8594";

            joiningList.addFirst(tempUser);
            joiningList.remove(pos+1);
            sendGroupMessage(GMMessage);
            sendGroupMessage(playerChange);
            sendGroupMessage(gmChange);
        }
    }
    
    
/**
     * Used to notify the server that a connection was closed.
     * The server then checks to see which user was connected to that connection
     * and removes them from the list and cleans things up. Usually called by the
     * thread that was handling the connection.
     * @param connection socket connection associated with the closed socket.
     */
    public void someoneLeft( Socket connection )
    {
        //ping all connections, if a connection errors, remove it
        //send message to all saying who left
        User temp_list;
        User newGM;
        String name = null;
        String temp_m;
        int pos;
        
        
        pos = findByConnection(connection);
        temp_list = (User)joiningList.get(pos);

        name = temp_list.getUsername();
        if( !name.isEmpty() )
        {
            temp_m = name+" command leaveRoom ";
            sendGroupMessage(temp_m);
        }
        
        if( pos == 0 && joiningList.size() > 1)
        {
            newGM = (User)joiningList.get(1);
            newGM.setCharacterName( "GM" );
            temp_m = name + " command makeGM " + newGM.getUsername() + " yatta ";
            sendGroupMessage(temp_m);
            String gmChange = newGM.getUsername() + " request charNameChange blah \u8594GM\u8594";
            sendGroupMessage(gmChange);
        }        
        joiningList.remove(pos);

        return;    
    }
    
    
    /**
     *Before executing a message from user or passing it on
     *verify that they have the right to send the message
     */
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private boolean verifyUser( String originName, Socket connection )
    {
        int namePos;
        int connectionPos;
        String errorMessage;
        
        namePos = findByName(originName);
        connectionPos = findByConnection( connection );
        

        if( (namePos != -1) && (namePos == connectionPos) )
        {
            return true;
        }
        else
        {
            errorMessage = "server chat systemMessage server " +
                    "The name you are using, " + originName + ", does not match the name stored for you. " +
                    "Try changing your name again.";
             serverthread.SendMessage(errorMessage, connection);
             return false;
        }
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private int findByConnection( Socket connection )
    {
        User aUser;
        Socket theirSocket;
        
        for ( int i = 0; i < (joiningList.size()); i++ )
        {
            aUser = (User)joiningList.get(i);
            theirSocket = aUser.getSock();
            
            if ( connection == theirSocket )
            {
                return i;
            }
        }
        return -1;
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private int findByName( String name )
    {
        for (int i = 0; i<(joiningList.size()); i++ )
        {
            User aUser = (User)joiningList.get(i);
           
            if( name.equalsIgnoreCase( aUser.getUsername()) )
            {
                return i;
            }
        }
        return -1;
    }
    
    
/********************************************************************************
  Method:
  Description:
  Parameters:
  Used by:
  Uses:
********************************************************************************/
    private void sendGroupMessage(String groupMessage)
    {
        User aUser;
        Socket theirSocket;
        
        for (int i = 0; i < joiningList.size(); i++ )
        {
            aUser = (User)joiningList.get(i);
            theirSocket = aUser.getSock();
            serverthread.SendMessage( groupMessage, theirSocket);
        }
    }
    
    
     /**
     *Before executing a GM command verify that user is the GM
     */
/**
     * Verifies that a particular connection is associated with the current GM.
     * @param connection to be tested for GMship.
     * @return true if connection is a GM, false if not.
     */
    public boolean verifyGM( Socket connection )
    {
        // if connection is joiningList(0) its the GM
        User temp_list;
        Socket temp_socket;

        temp_list = (User) joiningList.get(0);
        temp_socket = temp_list.getSock();
        
        if ( temp_socket == connection )
        {
            return true;
        }
        return false;
    }
    
    
/**
     * Shuts down the server.
     * Only closes the listener, does not disconnect all users.
     */
    public void Shutdown()
    {
        serverthread.StopServer();
        //tempmain.isServer = false;
    }
    
    
    
/**
     * Class constructor for Server.
     * Sets up the server to run, and starts it.
     * @param port Port that the server will be listening for connections on.
     * @param gui Current instance of d20chatui.
     * @param main Current instance of D20Chat.
     * @param tpassword Password that the server will use.
     */
    public Server(int port, D20Chatui gui, D20Chat main, String tpassword )
    {
        tempui = gui;
        tempmain = main;
        password = tpassword;
       
        joiningList = new LinkedList<Object> ();
        //create the server thread, pass it gui so that it can post directly
        //and then start up the server thread
        serverthread = new ServerListener(this);
        serverthread.StartServer(port);
        //start up the user classes if needed, and any other crap or variables you might need
    }
    
    
/**
     * Displays error messages and system messages to the user.
     * Often times used to display more than just system and error messages.
     * @param message Error or system message to be displayed.
     */
    public void ErrorMessageHandler(String message)
    {
        tempui.tempchatwindowoutput( message );
        
    }
    
    
/**
     * Sets up variables saying the server is running and greys out certain options.
     */
    public void startServerTasks() 
    {
        // Check if server was started, if so, then disable "Start Server"
        //  option from "File" menu, enable "Stop Server", and
        //  change GUI title to indicate that user is hosting the server

            tempui.showMenuStartServer(false);
            tempui.showMenuStopServer(true);
            tempmain.isServer = true;
            tempui.changeGuiTitle("D20 Chat - Hosting - " + tempmain.serverIP);

            // Enable "GM" specific tasks if user IS the server
            //tempui.showMenuSilence(true);
            //tempui.showMenuLock(true);

    }
    
    
/**
     * Ungreys and greys certain options, and sets the program to being ready to host.
     */
    public void stopServerTasks() {
        
            tempui.showMenuStartServer(true);
            tempui.showMenuStopServer(false);
            tempmain.isServer = false;
            tempui.changeGuiTitle("D20 Chat");
            
            // Disable "GM" specific tasks
            //tempui.showMenuSilence(false);
            //tempui.showMenuLock(false);
    }

    /**
     * @return the currentmap
     */
    public String getCurrentmap() {
        return currentmap;
    }

    /**
     * @param currentmap the currentmap to set
     */
    public void setCurrentmap(String currentmap) {
        this.currentmap = currentmap;
    }



    
}

